#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <types.h>
#include <utils.h>
#include <unistd.h>
#include <syserr.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <net/if_arp.h>
#include "device.h"
#include "evtloop.h"
#include "netcore.h"

#define LOCAL_START_PORT    10000

int net_ip4_addr(const ip_addr_t *ip, u16 port, struct sockaddr_in *addr)
{
    memset(addr, 0, sizeof(*addr));
    addr->sin_family      = AF_INET;
    addr->sin_port        = htons(port);
    addr->sin_addr.s_addr = ip_addr_get_ip4_u32(ip);

    return 0;
}
void net_addr_from_sockaddr(const struct sockaddr_in *addr, ip_addr_t *ip, u16 *port)
{
    ip_addr_set_ip4_u32(ip, addr->sin_addr.s_addr);
    if (port)
        *port = ntohs(addr->sin_port);
}

static int net_new_socket(int domain, int type, int protocol)
{
    int sockfd;

    sockfd = socket(domain, type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol);
    if (sockfd != -1)
        return sockfd;

    DEV_PE("socket open error[%s]!\n", strerror(errno));
    return -errno;
}

int socket_new(device_t *dev, const char *ethname, int type, const ip_addr_t *ip, u16 port)
{
    int sock;

    if (ethname && !net_get_if_status(ethname))
    {
        DEV_PD("<%s>: dev[%s] not ready!\n", dev_name(dev), ethname);
        return -1;
    }
    if ((sock = net_new_socket(PF_INET, type, 0)) < 0)
        return sock;

    if (type == SOCK_STREAM && net_tcp_nodelay(sock, 1) < 0)
    {
        DEV_PE("<%s>: net_tcp_nodelay call fault!\n", dev_name(dev));
        goto fail;
    }

    if (ethname && net_bindto_eth(sock, ethname) < 0)
    {
        DEV_PE("<%s>: net_bindto_eth call fault, ethname: %s!\n", dev_name(dev), ethname);
        goto fail;
    }

    dev_fd(dev) = sock;
    if (port != 0 && net_socket_bind(dev, ip, port) < 0)
    {
        DEV_PE("<%s>: bind to port[%d] call fault!\n", dev_name(dev), port);
        goto fail;
    }

    return sock;
fail:
    dev_fd(dev) = -1;
    close(sock);
    return -1;
}
int socket_new_local(device_t *dev, int type, const char *path)
{
    int sock;

    if ((sock = net_new_socket(PF_UNIX, type, 0)) < 0)
        return sock;

    dev_fd(dev) = sock;
    if (path && path[0] != '\0' && net_socket_bind_to_path(dev, path) < 0)
        goto fail;

    return sock;

fail:
    dev_fd(dev) = -1;
    close(sock);
    return -1;
}
void socket_del(int sock)
{
    close(sock);
}

int net_socket_bind(device_t *dev, const ip_addr_t *ip, u16 port)
{
    int on = 1;
    if (setsockopt(dev_fd(dev), SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
        return -1;

    struct sockaddr_in addr;
    net_ip4_addr(ip, port, &addr);

    DEV_PI("<%s>: bind to %s:%d...\n", dev_name(dev), ipaddr_ntoa(ip), port);

    return bind(dev_fd(dev), (struct sockaddr *)&addr, sizeof(addr));
}
int net_socket_bind_to_path(device_t *dev, const char *name)
{
    struct sockaddr_un addr;
    int r;

    DEV_PI("<%s>: bind to %s...\n", dev_name(dev), name);

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    svc__strscpy(addr.sun_path, name, sizeof(addr.sun_path));
    if (addr.sun_path[0] == '#')
        addr.sun_path[0] = '\0';
    else
        unlink(addr.sun_path);

    r = bind(dev_fd(dev), (struct sockaddr *)&addr, sizeof(addr));
    if (r < 0)
        DEV_PE("<%s>: bind error[%s]!\n", dev_name(dev), strerror(errno));
    return r;
}

int net_conn(device_t *dev, const ip_addr_t *ip, u16 port)
{
    int err = 0;
    struct sockaddr_in addr;

    if (ip_addr_isany(ip))
    {
        DEV_PE("<%s>: peerip[0.0.0.0] invalid!\n", dev_name(dev));
        return -EINVAL;
    }

    net_ip4_addr(ip, port, &addr);
    do
    {
        errno = 0;
        err = connect(dev_fd(dev), (struct sockaddr *)&addr, sizeof(addr));
    }
    while (err == -1 && errno == EINTR);
    if (err < 0 && errno != 0)
    {
        if (errno == EINPROGRESS)
            return 0;

        err = -errno;
        DEV_PE("<%s>: connect error: %s\n", dev_name(dev), strerror(errno));
    }
    return err;
}
int net_conn_to_path(device_t *dev, const char *path)
{
    struct sockaddr_un addr;
    int err;

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    svc__strscpy(addr.sun_path, path, sizeof(addr.sun_path));

    do
        err = connect(dev_fd(dev), (struct sockaddr *)&addr, sizeof(addr));
    while (err == -1 && errno == EINTR);

    if (err < 0 && errno != 0)
    {
        if (errno == EINPROGRESS)
            return 0;

        err = -errno;
        DEV_PE("<%s>: connect error: %s\n", dev_name(dev), strerror(errno));
    }
    return err;
}

int net_accept(int sock, ip_addr_t *ip, u16 *port)
{
    int ret;
    struct sockaddr_in addr;
    socklen_t len = sizeof(struct sockaddr_in);

    ret = accept(sock, (struct sockaddr *)&addr, &len);
    if (ret > 0)
        net_addr_from_sockaddr(&addr, ip, port);
    return ret;
}

int net_tcp_nodelay(int sock, int on)
{
    if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)))
        return -1;
    return 0;
}

int net_nonblock(int sock, int set)
{
    int r;

    do
    {
        r = ioctl(sock, FIONBIO, &set);
    }
    while (r == -1 && errno == EINTR);

    if (r)
        return -EIO;

    return 0;
}
int net_cloexec_ioctl(int sock, int set)
{
    int r;

    do
        r = ioctl(sock, set ? FIOCLEX : FIONCLEX);
    while (r == -1 && errno == EINTR);

    if (r)
        return -EIO;

    return 0;
}

int net_bindto_eth(int sock, const char *ethname)
{
    struct ifreq ifr;

    memset(&ifr, 0x00, sizeof(ifr));
    svc__strscpy(ifr.ifr_name, ethname, sizeof(ifr.ifr_name));

    return setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr));
}

/*
 * return: 1: link up, 0: link down, -1: error.
 */
int net_get_if_status(const char *ifname)
{
    int ret = 0;

    char name[0x40];
    snprintf(name, sizeof(name), "/sys/class/net/%s/operstate", ifname);

    FILE *f = fopen(name, "r");
    if (!f) return 0;

    char line[0x40];
    if (fgets(line, sizeof(line), f))
    {
        if (!memcmp(line, "up", strlen("up")) ||
            !memcmp(line, "unknown", strlen("unknown")))
            ret = 1;
    }

    fclose(f);
    return ret;
}

int net_get_sock_ip(int sock, const char *ifname, ip_addr_t *ip)
{
    struct ifreq ifr;

    strcpy(ifr.ifr_name, ifname);
    if (ioctl(sock, SIOCGIFADDR, &ifr))
        return -1;

    net_addr_from_sockaddr((struct sockaddr_in *)&ifr.ifr_addr, ip, NULL);
    return 0;
}

int net_get_if_ip(const char *ifname, ip_addr_t *ip)
{
    struct ifreq ifr;
    struct sockaddr_in *sin;

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        perror("socket");
        return -1;
    }

    strcpy(ifr.ifr_name, ifname);

    sin = (struct sockaddr_in *)&ifr.ifr_addr;
    sin->sin_family = AF_INET;
    if (ioctl(sock, SIOCGIFADDR, &ifr))
    {
        close(sock);
        return -1;
    }

    net_addr_from_sockaddr(sin, ip, NULL);
    close(sock);
    return 0;
}

int net_get_if_mac(const char *ifname, char *mac)
{
    struct ifreq ifr;
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        perror("socket");
        return -1;
    }

    strcpy(ifr.ifr_name, ifname);
    ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
    if (ioctl(sock, SIOCGIFHWADDR, &ifr))
    {
        close(sock);
        return -1;
    }

    memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
    close(sock);
    return 0;
}
int net_set_if_mac(const char *ifname, char *mac)
{
    struct ifreq ifr;

    memset(&ifr, 0,sizeof(ifr));
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        perror("socket");
        return -1;
    }

    strcpy(ifr.ifr_name, ifname);
    ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
    if (ioctl(sock, SIOCGIFHWADDR, &ifr))
    {
        close(sock);
        return -1;
    }

    memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);
    if (ioctl(sock, SIOCSIFHWADDR, &ifr))
    {
        perror("set mac addr fail");
        close(sock);
        return -1;
    }

    close(sock);
    return 0;
}

u16 get_local_port_rand(int type)
{
    static u8 rand_port;

    rand_port++;

    srand(hw_time());

    int tcp = type == SOCK_STREAM ? 1 : 0;

    return LOCAL_START_PORT + rand() % 1000 + tcp * 100 + rand_port % 100;
}
